/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2001 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "rwops.h"
#include "error.h"
#include "debug.h"
#include "misc.h"

namespace SEL
{

// ! ! !
// Should profile this (virtual begin / virtual end)

int
RWops::seekMarshaller (SDL::RWops *context, int offset, int whence)
{
	RWops *ops = (RWops*)context->hidden.unknown.data1;
	int begin = ops->m_virtual_begin;
	int size = ops->m_virtual_size;
	if (whence == SEEK_SET)
		offset += begin;
	if (whence == SEEK_END && size != 0)
	{
		whence = SEEK_SET;
		offset += begin + size;
	}
	ops->m_position = ops->doSeek (offset, whence);
	return (ops->m_position - begin);
}

int
RWops::readMarshaller (SDL::RWops *context, void *ptr, int size, int maxnum)
{
	RWops *ops = (RWops*)context->hidden.unknown.data1;

	int res = ops->doRead (ptr, size, maxnum);

	if (res <= 0)
		return res;

	if (ops->m_virtual_size != 0 &&
	    (ops->m_position + size * res >
	     ops->m_virtual_begin + ops->m_virtual_size))
	{
		res = ((ops->m_virtual_begin + ops->m_virtual_size -
		         ops->m_position) / size);
	}

	ops->m_position += res * size;

	return res;
}

int
RWops::writeMarshaller (SDL::RWops *context, const void *ptr, int size,
			int num)
{
	return ((RWops*)context->hidden.unknown.data1)
		->doWrite (ptr, size, num);
}

int
RWops::closeMarshaller (SDL::RWops *context)
{
	int res = ((RWops*)context->hidden.unknown.data1)->doClose ();
	delete context;
	return res;
}

RWops::RWops ()
{
	m_big_endian = false;
	m_virtual_begin = 0;
	m_virtual_size = 0;
	m_rwops = NULL;
	m_status = STAT_OK;
	m_rwops = new SDL::RWops;
	if (!m_rwops)
	{
		warning ("Couldn't allocate a SDL::RWops!");
		throw Error ("Out of memory.");
	}
	m_rwops->hidden.unknown.data1 = (void*)this;
	m_rwops->seek = seekMarshaller;
	m_rwops->read = readMarshaller;
	m_rwops->write = writeMarshaller;
	m_rwops->close = closeMarshaller;
	m_position = 0;
}

RWops::~RWops ()
{
	if (m_rwops)
	{
		// hmm.. cool
		warning ("Destroying unclosed SEL::RWops. (Haven't you" << std::endl <<
		         "  forgotten to call the close () function in your destructor?)");
		del (m_rwops);
		m_rwops = NULL;
	}
}

int
RWops::close ()
{
	if (!m_rwops)
	{
		return -1;
	}
	int i = SDL::RWclose (m_rwops);
	m_rwops = NULL;
	return i;
}

void
RWops::pushPosition ()
{
	m_pos_stack.push (tell ());
}

int
RWops::popPosition ()
{
	int pos = m_pos_stack.top ();
	m_pos_stack.pop ();
	return seek (pos, SEEK_SET);
}

#define __write_value(name) \
	updateStatus (write (&name, (sizeof (name)), 1));

RWops &
RWops::operator<< (Uint8 value)
{
	__write_value (value);
	return *this;
}


RWops &
RWops::operator<< (Sint8 value)
{
	__write_value (value);
	return *this;
}

#define __correct_value(name, size)                 \
	if (bigEndian ())                           \
		name = SDL::SwapBE##size (name);    \
	else                                        \
		name = SDL::SwapLE##size (name);

RWops &
RWops::operator<< (Uint16 value)
{
	__correct_value (value, 16);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (Sint16 value)
{
	__correct_value (value, 16);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (Uint32 value)
{
	__correct_value (value, 32);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (Sint32 value)
{
	__correct_value (value, 32);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (Uint64 value)
{
	__correct_value (value, 64);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (Sint64 value)
{
	__correct_value (value, 64);
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (char value)
{
	__write_value (value);
	return *this;
}

RWops &
RWops::operator<< (const std::string & value)
{
	*this << (Uint32)value.length ();
	updateStatus (write (value.c_str(), value.length (), 1));
	return *this;
}

#define __read_value(name) \
	updateStatus (read (&name, (sizeof (name)), 1));

RWops &
RWops::operator>> (Uint8 &value)
{
	__read_value (value);
	return *this;
}

RWops &
RWops::operator>> (Sint8 &value)
{
	__read_value (value);
	return *this;
}

RWops &
RWops::operator>> (Uint16 &value)
{
	__read_value (value);
	__correct_value (value, 16);
	return *this;
}

RWops &
RWops::operator>> (Sint16 &value)
{
	__read_value (value);
	__correct_value (value, 16);
	return *this;
}

RWops &
RWops::operator>> (Uint32 &value)
{
	__read_value (value);
	__correct_value (value, 32);
	return *this;
}

RWops &
RWops::operator>> (Sint32 &value)
{
	__read_value (value);
	__correct_value (value, 32);
	return *this;
}

RWops &
RWops::operator>> (Uint64 &value)
{
	__read_value (value);
	__correct_value (value, 64);
	return *this;
}

RWops &
RWops::operator>> (Sint64 &value)
{
	__read_value (value);
	__correct_value (value, 64);
	return *this;
}

RWops &
RWops::operator>> (char &value)
{
//	__read_value (value);
	value = getChar ();
	return *this;
}

RWops &
RWops::operator>> (std::string & value)
{
	Uint32 len;
	*this >> len;
	char *str = new char[len];
	updateStatus (read (str, len, 1));
	value.assign (str, len);
	delete[] str;
	return *this;
}

#undef __correct_value
#undef __read_value
#undef __write_value

FileRWops::FileRWops (const std::string & filename, const std::string & mode)
{
	m_file_rwops = SDL::RWFromFile (filename.c_str (), mode.c_str ());
	if (!m_file_rwops)
	{
		warning ("Couldn't create RWops from file " << filename << " !");
		throw Error (SDL::GetError ());
	}
}

FileRWops::FileRWops (FILE *fp, bool autoclose)
{
	m_file_rwops = SDL::RWFromFP (fp, autoclose);
	if (!m_file_rwops)
	{
		warning ("Couldn't create RWops from FP !");
		throw Error (SDL::GetError ());
	}
}

FileRWops::~FileRWops ()
{
	close ();
}

int
FileRWops::doSeek (int offset, int whence)
{
	return SDL::RWseek (m_file_rwops, offset, whence);
}

int
FileRWops::doRead (void *ptr, int size, int maxnum)
{
	return SDL::RWread (m_file_rwops, ptr, size, maxnum);
}

int
FileRWops::doWrite (const void *ptr, int size, int num)
{
	return SDL::RWwrite (m_file_rwops, ptr, size, num);
}

int
FileRWops::doClose ()
{
	return SDL::RWclose (m_file_rwops);
}

char
FileRWops::getChar ()
{
	int ch = getc (m_file_rwops->hidden.stdio.fp);
	if (ch == EOF) m_status |= STAT_EOF;
	return (char)ch;
}

MemRWops::MemRWops (void *mem, int size)
{
	m_mem_rwops = SDL::RWFromMem (mem, size);
	if (!m_mem_rwops) {
		warning ("Couldn't create RWops from memory !");
		throw Error (SDL::GetError ());
	}
}

MemRWops::~MemRWops ()
{
	close ();
}

int
MemRWops::doSeek (int offset, int whence)
{
	return SDL::RWseek (m_mem_rwops, offset, whence);
}

int
MemRWops::doRead (void *ptr, int size, int maxnum)
{
	return SDL::RWread (m_mem_rwops, ptr, size, maxnum);
}

int
MemRWops::doWrite (const void *ptr, int size, int num)
{
	return SDL::RWwrite (m_mem_rwops, ptr, size, num);
}

int
MemRWops::doClose ()
{
	return SDL::RWclose (m_mem_rwops);
}

char
MemRWops::getChar ()
{
	char ch;
	updateStatus (read (&ch, (sizeof (ch)), 1));
	return ch;
}

} // namespace SEL
